import requests as re
import time as t
import random
import copy

# Constants for score evaluations
class SCORE:
    FIVE = 10000
    FOUR = 1000
    BLOCKED_FOUR = 500
    THREE = 100

# Constants for MIN and MAX values
MIN = float('-inf')
MAX = float('inf')

# Cache dictionary to store evaluated board states
Cache = {}

# Placeholder for configuration
config = {
    'vcxDeep': 5,
    'cache': True,
    'debug': False,
}

# Placeholder for the board's zobrist hashing and other methods
class Board:
    def __init__(self):
        self.board = ['.' for _ in range(15 * 16)]
        self.zobrist = self.Zobrist()
        self.comScore = [[0 for _ in range(15)] for _ in range(15)]
        self.humScore = [[0 for _ in range(15)] for _ in range(15)]
        # Initialize other attributes as needed

    class Zobrist:
        def __init__(self):
            self.code = 0  # Placeholder for Zobrist hash code
    def evaluate(self, role):
        RWP = RuleWithPoints()
        return evaluate_board(self.board, RWP)
    def gen(self, role, step1, step2):
        return get_valid_moves(self.board)
    def put(self, p, role):
        index = getIndexNum(p)
        self.board = self.board[:index] + role + self.board[index + 1:]
    def remove(self, p):
        index = getIndexNum(p)
        self.board = self.board[:index] + '.' + self.board[index + 1:]

    def toString(self):
        return ''.join(self.board)

    def win(self):
        # Placeholder for checking the winner
        return None

board = Board()
debugNodeCount = 0

def RuleWithPoints():
    RWP = {
        ("CMMMM", "MCMMM", "MMCMM", "MMMCM", "MMMMC"): 10000,
        ("COOOO", "OCOOO", "OOCOO", "OOOCO", "OOOOC"): 6000,
        (".CMMM.", ".MCMM.", ".MMCM.", ".MMMC."): 5000,
        ("COOO.", ".OOOC", ".OOCO.", ".OCOO."): 2500,
        ("OCMMM.", "OMCMM.", "OMMCM.", "OMMMC.", ".CMMMO", ".MCMMO", ".MMCMO", ".MMMCO"): 2000,
        (".MMC.", ".MCM.", ".CMM."): 400,
        (".OOC", "COO.", "MOOOC", "COOOM"): 400,
        (".MMCO", ".MCMO", ".CMMO", "OMMC.", "OMCM.", "OCMM.", "MOOC", "COOM"): 200,
        (".MC.", ".CM."): 50, ("."): 1,
    }
    return RWP

def evaluate_board(board, RWP):
    points = 0
    board_str = ''.join(board)
    for rules, value in RWP.items():
        for rule in rules:
            points += board_str.count(rule) * value
    return points

def get_valid_moves(board):
    return [i for i, cell in enumerate(board) if cell == '.']

def make_move(board, move, player):
    return board[:move] + player + board[move + 1:]

def is_terminal_node(board):
    return 'CMMMM' in board or 'COOOO' in board or '.' not in board

def minimax(board, depth, alpha, beta, maximizingPlayer, RWP, memo):
    board_tuple = tuple(board)
    if board_tuple in memo:
        return memo[board_tuple]

    if depth == 0 or is_terminal_node(board):
        eval = evaluate_board(board, RWP)
        memo[board_tuple] = eval
        return eval

    if maximizingPlayer:
        maxEval = float('-inf')
        for move in get_valid_moves(board):
            new_board = make_move(board, move, 'M')  # 'M' for maximizer
            eval = minimax(new_board, depth - 1, alpha, beta, False, RWP, memo)
            maxEval = max(maxEval, eval)
            alpha = max(alpha, eval)
            if beta <= alpha:
                break
        memo[board_tuple] = maxEval
        return maxEval
    else:
        minEval = float('inf')
        for move in get_valid_moves(board):
            new_board = make_move(board, move, 'O')  # 'O' for minimizer
            eval = minimax(new_board, depth - 1, alpha, beta, True, RWP, memo)
            minEval = min(minEval, eval)
            beta = min(beta, eval)
            if beta <= alpha:
                break
        memo[board_tuple] = minEval
        return minEval

def get_best_move(board, RWP, depth):
    best_move = None
    maxEval = float('-inf')
    memo = {}
    for move in get_valid_moves(board):
        new_board = make_move(board, move, 'M')
        eval = minimax(new_board, depth - 1, float('-inf'), float('inf'), False, RWP, memo)
        if eval > maxEval:
            maxEval = eval
            best_move = move
    return best_move

def getMaxCoords(Order, RWP, indexSrc):
    board = ['.' for _ in range(15 * 16)]
    step = 0
    BW = judge(Order)

    for i in range(0, len(Order), 2):
        index = getIndexNum(Order[i:i + 2])
        if (step % 2) == 0:
            board[index] = BW[0]
        else:
            board[index] = BW[1]
        step += 1

    print(''.join(board))  # 测试显示数据用

    maxCoord = ''
    maxPoints = 0
    for i in range(len(board)):
        if board[i] == '.':
            tempBoard = board[:i] + ['C'] + board[i + 1:]  # Assume 'M' makes move
            coord = indexSrc[i]
            lines4 = ','.join(getLine(coord, tempBoard))
            points = 0
            for rules, value in RWP.items():
                for rul in range(0, len(rules)):
                    if rules[rul] in lines4:
                        points += value * lines4.count(rules[rul])

            if points > maxPoints:
                maxPoints = points
                maxCoord = coord

    print(f"{maxCoord} {maxPoints}", end=' ')
    return maxCoord

def findMax(role, score):
    result = []
    fives = []
    for i in range(len(board.board)):
        for j in range(len(board.board[i])):
            if board.board[i][j] == R.EMPTY:
                p = [i, j]
                s = board.comScore[p[0]][p[1]] if role == R.COM else board.humScore[p[0]][p[1]]
                p.append(s)  # Attach score to the point
                if s >= score:
                    result.append(p)
                if s >= SCORE.FIVE:
                    fives.append(p)
    if fives:
        return fives
    result.sort(key=lambda x: x[2], reverse=True)
    return result

def findMin(role, score):
    result = []
    fives = []
    fours = []
    blockedfours = []
    for i in range(len(board.board)):
        for j in range(len(board.board[i])):
            if board.board[i][j] == R.EMPTY:
                p = [i, j]
                s1 = board.comScore[p[0]][p[1]] if role == R.COM else board.humScore[p[0]][p[1]]
                s2 = board.humScore[p[0]][p[1]] if role == R.COM else board.comScore[p[0]][p[1]]
                if s1 >= SCORE.FIVE:
                    p.append(-s1)
                    return [p]
                if s2 >= SCORE.FIVE:
                    p.append(s2)
                    fives.append(p)
                    continue
    if fives:
        return fives
    if fours:
        return fours + blockedfours
    result = blockedfours + result
    result.sort(key=lambda x: abs(x[2]), reverse=True)
    return result

def max(role, deep, totalDeep):
    global debugNodeCount
    debugNodeCount += 1
    if deep <= 1:
        return False
    points = findMax(role, MAX_SCORE)
    if points and points[0][2] >= SCORE.FOUR:
        return [points[0]]
    if not points:
        return False
    for p in points:
        board.put(p, role)
        if not p[2] <= -SCORE.FIVE:
            lastMaxPoint = p
        m = min(R.reverse(role), deep - 1)
        board.remove(p)
        if m:
            if len(m):
                m.insert(0, p)
                return m
            else:
                return [p]
    return False

def min(role, deep):
    global debugNodeCount
    debugNodeCount += 1
    w = board.win()
    if w == role:
        return False
    if w == R.reverse(role):
        return True
    if deep <= 1:
        return False
    points = findMin(role, MIN_SCORE)
    if not points:
        return False
    if points and -points[0][2] >= SCORE.FOUR:
        return False
    cands = []
    for p in points:
        board.put(p, role)
        lastMinPoint = p
        m = max(R.reverse(role), deep - 1)
        board.remove(p)
        if m:
            m.insert(0, p)
            cands.append(m)
        else:
            return False
    result = random.choice(cands)
    return result

def deeping(role, deep, totalDeep):
    bestMove = None
    bestScore = MIN
    for d in range(2, deep + 1, 2):
        score = max(role, d, totalDeep)
        if score > bestScore:
            bestScore = score
            bestMove = score[0]
        if bestScore >= SCORE.FIVE:
            break
    return bestMove

def vcx(role, deep, onlyFour):
    deep = deep if deep is not None else config['vcxDeep']
    if deep <= 0:
        return False
    if onlyFour:
        MAX_SCORE = SCORE.BLOCKED_FOUR
        MIN_SCORE = SCORE.FIVE
        result = deeping(role, deep, deep)
        if result:
            result['score'] = SCORE.FOUR
            return result
        return False
    else:
        MAX_SCORE = SCORE.THREE
        MIN_SCORE = SCORE.BLOCKED_FOUR
        result = deeping(role, deep, deep)
        if result:
            result['score'] = SCORE.THREE * 2  # Double three
        return result

def vcf(role, deep):
    c = getCache(True)
    if c:
        return c
    result = vcx(role, deep, True)
    cache(result, True)
    return result

def vct(role, deep):
    c = getCache()
    if c:
        return c
    result = vcx(role, deep, False)
    cache(result)
    return result

def cache(result, is_vcf=False):
    if not config['cache'] or result is None:
        return False
    Cache[board.zobrist.code] = result

def getCache(is_vcf=False):
    if not config['cache']:
        return None
    return Cache.get(board.zobrist.code)

# Helper functions for encoding and playing
def fastModular(x):
    result = 1
    while (x[1] > 0):
        if (x[1] & 1):
            result = result * x[0] % x[2]
        x[1] = int(x[1] / 2)
        x[0] = x[0] * x[0] % x[2]
    return result

def str_to_num(strings):
    sum = 0
    lens = len(strings)
    for i in range(0, lens):
        sum += ord(strings[i]) * 256 ** (lens - i - 1)
    return sum

def encodeLogin(password):
    power = 65537
    modulus = 135261828916791946705313569652794581721330948863485438876915508683244111694485850733278569559191167660149469895899348939039437830613284874764820878002628686548956779897196112828969255650312573935871059275664474562666268163936821302832645284397530568872432109324825205567091066297960733513602409443790146687029
    return hex(fastModular([str_to_num(password), power, modulus]))

def join_game(user, myHexPass):
    url = 'http://183.175.12.27:8002/join_game'
    param = {
        'user': user,
        'password': myHexPass,
        'data_type': 'json'
    }
    getHtml = re.get(url, params=param)
    print(f"Open a new game {getHtml.text}")
    return getHtml

def check_game(game_id):
    url = 'http://183.175.12.27:8002/check_game/' + str(game_id)
    getState = re.get(url)
    return getState

def play_game(user, myHexPass, game_id, coord):
    url = 'http://183.175.12.27:8002/play_game/' + str(game_id)
    param = {
        'user': user,
        'password': myHexPass,
        'data_type': 'json',
        'coord': coord
    }
    re.get(url, params=param)

def getIndexNum(coords):
    return (ord(coords[0]) - ord('a')) * 16 + ord(coords[1]) - ord('a')

def allIndexStr():
    spot = []
    for i in range(0, 15):
        for j in range(0, 16):
            spot.append(chr(i + 97) + chr(j + 97))
    return spot

def getLine(coord, board):
    line = ['', '', '', '']
    i = 0
    while (i != 15):
        if ord(coord[1]) - ord('a') - 7 + i in range(0, 15):
            line[0] += board[(ord(coord[0]) - ord('a')) * 16 + ord(coord[1]) - ord('a') - 7 + i]
        else:
            line[0] += ' '
        if ord(coord[0]) - ord('a') - 7 + i in range(0, 15):
            line[2] += board[(ord(coord[0]) - ord('a') - 7 + i) * 16 + ord(coord[1]) - ord('a')]
        else:
            line[2] += ' '
        if ord(coord[1]) - ord('a') - 7 + i in range(0, 15) and ord(coord[0]) - ord('a') - 7 + i in range(0, 15):
            line[1] += board[(ord(coord[0]) - ord('a') - 7 + i) * 16 + ord(coord[1]) - ord('a') - 7 + i]
        else:
            line[1] += ' '
        if ord(coord[1]) - ord('a') + 7 - i in range(0, 15) and ord(coord[0]) - ord('a') - 7 + i in range(0, 15):
            line[3] += board[(ord(coord[0]) - ord('a') - 7 + i) * 16 + ord(coord[1]) - ord('a') + 7 - i]
        else:
            line[3] += ' '
        i += 1
    return line

def judge(testOrder):
    if (len(testOrder) // 2) % 2 == 0:
        return 'MO'
    else:
        return 'OM'

user = '0211423595'
password = 'ys100820'
myHexPass = encodeLogin(password)
RWP = RuleWithPoints()
indexSrc = allIndexStr()

game_id = join_game(user, myHexPass).json()["game_id"]
state = check_game(game_id).json()

print("Looking for game partners ...")
while state['ready'] == "False":
    state = check_game(game_id).json()
    print(state['ready'], end=" ")
    t.sleep(5)

if state['creator'] != user:
    opponent = state['creator']
else:
    opponent = state['opponent_name']

while state['ready'] == "True":
    if state['current_turn'] == user:
        order = state['board']
        coord = getMaxCoords(order, RWP, indexSrc)
        play = play_game(user, myHexPass, game_id, coord)
        print(f"Playing {coord}")
    else:
        print(f"Waiting for {opponent} to play")

    t.sleep(5)
    state = check_game(game_id).json()

    if state['winner'] != "None":
        print(f"The winner is {state['winner']}")
        break
